All files / local eager_garbage_collector.ts

100% Statements 38/38
100% Branches 4/4
100% Functions 13/13
100% Lines 34/34
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104                                2x           2x                     2x 1001x         1001x           1001x   2x 1835x 1835x     2x 18x 18x     2x 1391x     2735x     2735x 2735x   2735x 956x 956x     956x 523x   956x           2735x 2735x     2x       956x 956x 2764x   2764x 2764x 808x   1956x         2x  
/**
 * Copyright 2017 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
 
import { DocumentKeySet, documentKeySet } from '../model/collections';
import { DocumentKey } from '../model/document_key';
 
import { GarbageCollector } from './garbage_collector';
import { GarbageSource } from './garbage_source';
import { PersistenceTransaction } from './persistence';
import { PersistencePromise } from './persistence_promise';
 
/**
 * A garbage collector implementation that eagerly collects documents as soon as
 * they're no longer referenced in any of its registered GarbageSources.
 *
 * This implementation keeps track of a set of keys that are potentially garbage
 * without keeping an exact reference count. During collectGarbage, the
 * collector verifies that all potential garbage keys actually have no
 * references by consulting its list of garbage sources.
 */
export class EagerGarbageCollector implements GarbageCollector {
  readonly isEager = true;
 
  /**
   * The garbage collectible sources to double-check during garbage collection.
   */
  private sources: GarbageSource[] = [];
 
  /**
   * A set of potentially garbage keys.
   * PORTING NOTE: This would be a mutable set if Javascript had one.
   */
  private potentialGarbage: DocumentKeySet = documentKeySet();
 
  addGarbageSource(garbageSource: GarbageSource): void {
    this.sources.push(garbageSource);
    garbageSource.setGarbageCollector(this);
  }
 
  removeGarbageSource(garbageSource: GarbageSource): void {
    this.sources.splice(this.sources.indexOf(garbageSource), 1);
    garbageSource.setGarbageCollector(null);
  }
 
  addPotentialGarbageKey(key: DocumentKey): void {
    this.potentialGarbage = this.potentialGarbage.add(key);
  }
 
  collectGarbage(
    txn: PersistenceTransaction | null
  ): PersistencePromise<DocumentKeySet> {
    const promises: Array<PersistencePromise<void>> = [];
    let garbageKeys = documentKeySet();
 
    this.potentialGarbage.forEach(key => {
      const hasRefsPromise = this.documentHasAnyReferences(txn, key);
      promises.push(
        hasRefsPromise.next(hasRefs => {
          // If there are no references, get the key.
          if (!hasRefs) {
            garbageKeys = garbageKeys.add(key);
          }
          return PersistencePromise.resolve();
        })
      );
    });
 
    // Clear locally retained potential keys and returned confirmed garbage.
    this.potentialGarbage = documentKeySet();
    return PersistencePromise.waitFor(promises).next(() => garbageKeys);
  }
 
  documentHasAnyReferences(
    txn: PersistenceTransaction | null,
    key: DocumentKey
  ): PersistencePromise<boolean> {
    const initial = PersistencePromise.resolve(false);
    return this.sources
      .map(source => () => source.containsKey(txn, key))
      .reduce<PersistencePromise<boolean>>((promise, nextPromise) => {
        return promise.next(result => {
          if (result) {
            return PersistencePromise.resolve(true);
          } else {
            return nextPromise();
          }
        });
      }, initial);
  }
}